-- CB40005.A
--
--                             Grant of Unlimited Rights
--
--     Under contracts F33600-87-D-0337, F33600-84-D-0280, MDA903-79-C-0687,
--     F08630-91-C-0015, and DCA100-97-D-0025, the U.S. Government obtained 
--     unlimited rights in the software and documentation contained herein.
--     Unlimited rights are defined in DFAR 252.227-7013(a)(19).  By making 
--     this public release, the Government intends to confer upon all 
--     recipients unlimited rights  equal to those held by the Government.  
--     These rights include rights to use, duplicate, release or disclose the 
--     released technical data and computer software in whole or in part, in 
--     any manner and for any purpose whatsoever, and to have or permit others 
--     to do so.
--
--                                    DISCLAIMER
--
--     ALL MATERIALS OR INFORMATION HEREIN RELEASED, MADE AVAILABLE OR
--     DISCLOSED ARE AS IS.  THE GOVERNMENT MAKES NO EXPRESS OR IMPLIED 
--     WARRANTY AS TO ANY MATTER WHATSOEVER, INCLUDING THE CONDITIONS OF THE
--     SOFTWARE, DOCUMENTATION OR OTHER INFORMATION RELEASED, MADE AVAILABLE 
--     OR DISCLOSED, OR THE OWNERSHIP, MERCHANTABILITY, OR FITNESS FOR A
--     PARTICULAR PURPOSE OF SAID MATERIAL.
--*
--
-- OBJECTIVE:
--      Check that exceptions raised in non-generic code can be handled by
--      a procedure in a generic package.  Check that the exception identity
--      can be properly retrieved from the generic code and used by the
--      non-generic code.
--
-- TEST DESCRIPTION:
--      This test models a possible usage paradigm for the type:
--        Ada.Exceptions.Exception_Occurrence.
--
--      A generic package takes access to procedure types (allowing it to
--      be used at any accessibility level) and defines a "fail soft"
--      procedure that takes designators to a procedure to call, a
--      procedure to call in the event that it fails, and a function to
--      call to determine the next action.
--
--      In the event an exception occurs on the call to the first procedure,
--      the exception is stored in a stack; along with the designator to the
--      procedure that caused it; allowing the procedure to be called again, 
--      or the exception to be re-raised.
--
--      A full implementation of such a tool would use a more robust storage
--      mechanism, and would provide a more flexible interface.
--
--
-- CHANGE HISTORY:
--      29 MAR 96   SAIC   Initial version
--      12 NOV 96   SAIC   Revised for 2.1 release
--
--!

----------------------------------------------------------------- CB40005_0

with Ada.Exceptions;
generic
  type Proc_Pointer is access procedure;
  type Func_Pointer is access function return Proc_Pointer;
package CB40005_0 is -- Fail_Soft


  procedure Fail_Soft_Call( Proc_To_Call : Proc_Pointer;
                            Proc_To_Call_On_Exception : Proc_Pointer := null;
                            Retry_Routine : Func_Pointer := null );

  function Top_Event_Exception return Ada.Exceptions.Exception_Occurrence;

  function Top_Event_Procedure return Proc_Pointer;

  procedure Pop_Event;

  function Event_Stack_Size return Natural;

end CB40005_0; -- Fail_Soft

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- CB40005_0

with Report;
package body CB40005_0 is

  type History_Event is record
    Exception_Event  : Ada.Exceptions.Exception_Occurrence_Access;
    Procedure_Called : Proc_Pointer;
  end record;

  procedure Store_Event( Proc_Called : Proc_Pointer;
                         Error       : Ada.Exceptions.Exception_Occurrence );

  procedure Fail_Soft_Call( Proc_To_Call : Proc_Pointer;
                            Proc_To_Call_On_Exception : Proc_Pointer := null;
                            Retry_Routine : Func_Pointer := null ) is

    Current_Proc_To_Call : Proc_Pointer := Proc_To_Call;

  begin
    while Current_Proc_To_Call /= null loop
      begin
        Current_Proc_To_Call.all;  -- call procedure through pointer
        Current_Proc_To_Call := null;
      exception
        when Capture: others =>
          Store_Event( Current_Proc_To_Call, Capture );
          if Proc_To_Call_On_Exception /= null then
            Proc_To_Call_On_Exception.all;
          end if;
          if Retry_Routine /= null then
            Current_Proc_To_Call := Retry_Routine.all;
          else
            Current_Proc_To_Call := null;
          end if;
      end;
    end loop;
  end Fail_Soft_Call;
  
  Stack : array(1..10) of History_Event;  -- minimal, sufficient for testing

  Stack_Top : Natural := 0;

  procedure Store_Event( Proc_Called : Proc_Pointer;
                         Error       : Ada.Exceptions.Exception_Occurrence )
  is
  begin
    Stack_Top := Stack_Top +1;
    Stack(Stack_Top) := ( Ada.Exceptions.Save_Occurrence(Error),
                          Proc_Called );
  end Store_Event;

  function Top_Event_Exception return Ada.Exceptions.Exception_Occurrence is
  begin
    if Stack_Top > 0 then
      return Stack(Stack_Top).Exception_Event.all;
    else
      return Ada.Exceptions.Null_Occurrence;
    end if;
  end Top_Event_Exception;

  function Top_Event_Procedure return Proc_Pointer is
  begin
    if Stack_Top > 0 then
      return Stack(Stack_Top).Procedure_Called;
    else
      return null;
    end if;
  end Top_Event_Procedure;

  procedure Pop_Event is
  begin
    if Stack_Top > 0 then
      Stack_Top := Stack_Top -1;
    else
      Report.Failed("Stack Error");
    end if;
  end Pop_Event;

  function Event_Stack_Size return Natural is
  begin
    return Stack_Top;
  end Event_Stack_Size;

end CB40005_0;

------------------------------------------------------------------- CB40005

with Report;
with TCTouch;
with CB40005_0;
with Ada.Exceptions;
procedure CB40005 is

  type Proc_Pointer is access procedure;
  type Func_Pointer is access function return Proc_Pointer;

  package Fail_Soft is new CB40005_0(Proc_Pointer, Func_Pointer);

  procedure Cause_Standard_Exception;

  procedure Cause_Visible_Exception;

  procedure Cause_Invisible_Exception;

  Exception_Procedure_Pointer : Proc_Pointer;

  Visible_Exception : exception;

  procedure Action_On_Exception;

  function Retry_Procedure return Proc_Pointer;

  Raise_Error : Boolean;

  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

  procedure Cause_Standard_Exception is
  begin
    TCTouch.Touch('S');  --------------------------------------------------- S
    if Raise_Error then
      raise Constraint_Error;
    end if;
  end Cause_Standard_Exception;

  procedure Cause_Visible_Exception is
  begin
    TCTouch.Touch('V');  --------------------------------------------------- V
    if Raise_Error then
      raise Visible_Exception;
    end if;
  end Cause_Visible_Exception;

  procedure Cause_Invisible_Exception is
    Invisible_Exception : exception;
  begin
    TCTouch.Touch('I');  --------------------------------------------------- I
    if Raise_Error then
      raise Invisible_Exception;
    end if;
  end Cause_Invisible_Exception;

  procedure Action_On_Exception is
  begin
    TCTouch.Touch('A');  --------------------------------------------------- A
  end Action_On_Exception;

  function Retry_Procedure return Proc_Pointer is
  begin
    TCTouch.Touch('R');  --------------------------------------------------- R
    return Action_On_Exception'Access;
  end Retry_Procedure;

         -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

begin  -- Main test procedure.

  Report.Test ("CB40005", "Check that exceptions raised in non-generic " &
                          "code can be handled by a procedure in a generic " &
                          "package.  Check that the exception identity can " &
                          "be properly retrieved from the generic code and " &
                          "used by the non-generic code" );

  -- first, check that the no exception cases cause no action on the stack
  Raise_Error := False;

  Fail_Soft.Fail_Soft_Call( Cause_Standard_Exception'Access );    -- S

  Fail_Soft.Fail_Soft_Call( Cause_Visible_Exception'Access,       -- V
                            Action_On_Exception'Access,
                            Retry_Procedure'Access );

  Fail_Soft.Fail_Soft_Call( Cause_Invisible_Exception'Access,     -- I
                            null,
                            Retry_Procedure'Access );

  TCTouch.Assert( Fail_Soft.Event_Stack_Size = 0, "Empty stack");

  TCTouch.Validate( "SVI", "Non error case check" );

  -- second, check that error cases add to the stack
  Raise_Error := True;

  Fail_Soft.Fail_Soft_Call( Cause_Standard_Exception'Access );    -- S

  Fail_Soft.Fail_Soft_Call( Cause_Visible_Exception'Access,       -- V
                            Action_On_Exception'Access,           -- A
                            Retry_Procedure'Access );             -- RA

  Fail_Soft.Fail_Soft_Call( Cause_Invisible_Exception'Access,     -- I
                            null,
                            Retry_Procedure'Access );             -- RA

  TCTouch.Assert( Fail_Soft.Event_Stack_Size = 3, "Stack = 3");

  TCTouch.Validate( "SVARAIRA", "Error case check" );

  -- check that the exceptions and procedure were stored correctly
  -- on the stack
  Raise_Error := False;

  -- return procedure pointer from top of stack and call the procedure
  -- through that pointer:

  Fail_Soft.Top_Event_Procedure.all;

  TCTouch.Validate( "I", "Invisible case unwind" );

  begin
    Ada.Exceptions.Raise_Exception( 
      Ada.Exceptions.Exception_Identity(Fail_Soft.Top_Event_Exception) );
    Report.Failed("1: Exception not raised");
  exception
    when Constraint_Error  => Report.Failed("1: Raised Constraint_Error");
    when Visible_Exception => Report.Failed("1: Raised Visible_Exception");
    when others            => null; -- expected case
  end;

  Fail_Soft.Pop_Event;

  -- return procedure pointer from top of stack and call the procedure
  -- through that pointer:

  Fail_Soft.Top_Event_Procedure.all;

  TCTouch.Validate( "V", "Visible case unwind" );

  begin
    Ada.Exceptions.Raise_Exception( 
      Ada.Exceptions.Exception_Identity(Fail_Soft.Top_Event_Exception) );
    Report.Failed("2: Exception not raised");
  exception
    when Constraint_Error  => Report.Failed("2: Raised Constraint_Error");
    when Visible_Exception => null; -- expected case
    when others            => Report.Failed("2: Raised Invisible_Exception");
  end;

  Fail_Soft.Pop_Event;

  Fail_Soft.Top_Event_Procedure.all;

  TCTouch.Validate( "S", "Standard case unwind" );

  begin
    Ada.Exceptions.Raise_Exception( 
      Ada.Exceptions.Exception_Identity(Fail_Soft.Top_Event_Exception) );
    Report.Failed("3: Exception not raised");
  exception
    when Constraint_Error  => null; -- expected case
    when Visible_Exception => Report.Failed("3: Raised Visible_Exception");
    when others            => Report.Failed("3: Raised Invisible_Exception");
  end;

  Fail_Soft.Pop_Event;

  TCTouch.Assert( Fail_Soft.Event_Stack_Size = 0, "Stack empty after pops");

  Report.Result;

end CB40005;
